(ns ru.nsu.fit.g7202.seml.sepath
  (:use [ru.nsu.fit.g7202.seml :as seml])
  (:use ru.nsu.fit.g7202.infix-ops)
  (:import ru.nsu.fit.g7202.seml.sepath.SEPathException))


(declare select)
(declare select1)


(defn- TODO [& args] (throw (SEPathException. "TODO")))


(defn- step [path-el obj]
    (cond
      (not (or (map? obj) (vector? obj)))
      (vector) #_(throw (SEPathException. (str "incorrect position of path element: '" path-el "'")))
         
      ; Фильтр
      (vector? path-el)
      (let [formula (map 
                      (fn [x] (if (list? x) 
                                (select1 obj x)
                                x))
                      path-el)
            formula-new (list 'ru.nsu.fit.g7202.infix-ops/with-infix-ops (apply list formula))
            res (eval formula-new)]
        (if res
          (vector obj)
          (vector)))
      
      ; Подвыражение на SEPath
      (list? path-el)
      (select obj path-el)
            
      (not (or (symbol? path-el) (keyword? path-el) (number? path-el)))
      (throw (SEPathException. (str "wrong path element (expected symbol, keyword or number): '" path-el "'")))

      ; *
      (= path-el '*)
      (cond 
        (map? obj) (vals obj)
        (vector? obj) obj)
         
      ; **
      (= path-el '**)
      (letfn [(fff [coll]
                (cond
                  (not (coll? coll)) 
                    (list coll)
                  (empty? coll)
                    (list)
                  (map? coll)
                    (cons coll (reduce concat (map fff (vals coll))))
                  :else
                    (cons coll (reduce concat (map fff coll)))))]
        (fff obj))
      
      (= path-el '|)
      (throw (SEPathException. (str "wrong path element: '" path-el "'")))
      
      ; :last в массиве
      (and (= path-el :last) (vector? obj))
      (vector (last obj))
      
      ; :count в массиве
      (and (= path-el :count) (vector? obj))
      (vector (count obj))
      
      ; :position в элементе массива
      (and (= path-el :position) (TODO obj))
      (TODO)
      
      ; Ключ в объекте / индекс в массиве
      :else
      (let [new-obj (get obj path-el)]
        (if-not (nil? new-obj)
          (vector new-obj)
          (vector)))
    ))

(defn- select-internal [start-obj objs path]
  (let [path-el (first path)
        path-rest (rest path)]
    (cond
      (nil? path-el)
      objs
      
      (= path-el '|)
      (concat
        objs
        (select-internal
          start-obj
          (vector start-obj)
          path-rest))
      
      :else
      (recur
        start-obj
        (reduce concat (map (partial step path-el) objs))
        path-rest)
    )))

(defn select [obj path]
  (select-internal
    obj
    (vector obj)
    path))

(defn select1 [obj path]
  (let [res (select obj path)]
    (if (= (count res) 1)
      (first res)
      (throw (SEPathException. (str "results count is not 1, it is: " (count res)))))))

